package com.allenja.eleviewer;

import java.io.IOException;
import java.util.List;

import android.content.Context;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;

public class CameraPreview extends ViewGroup implements SurfaceHolder.Callback {
	private final String TAG = "Camera Preview";

	private SurfaceView cameraSurfaceView;
	private SurfaceHolder cameraSurfaceHolder;
	private Size cameraPreviewSize;
	private List<Size> supportedPreviewSizes;
	private Camera camera;

	@SuppressWarnings("deprecation")
	CameraPreview(Context context) {
		super(context);

		cameraSurfaceView = new SurfaceView(context);
		addView(cameraSurfaceView);

		// Install a SurfaceHolder.Callback so we get notified when the underlying surface is created and destroyed.
		cameraSurfaceHolder = cameraSurfaceView.getHolder();
		cameraSurfaceHolder.addCallback(this);
		cameraSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
	}

	public void setCamera(Camera camera) {
		this.camera = camera;
		
		if (camera != null) {
			supportedPreviewSizes = camera.getParameters().getSupportedPreviewSizes();
			requestLayout();
		}
	}

	public void switchCamera(Camera camera) {
		setCamera(camera);
		
		try {
			camera.setPreviewDisplay(cameraSurfaceHolder);
			
		} catch (IOException exception) {
			Log.e(TAG, "IOException caused by setPreviewDisplay()",
					exception);
		}
		
		Camera.Parameters parameters = camera.getParameters();
		parameters.setPreviewSize(cameraPreviewSize.width, cameraPreviewSize.height);
		requestLayout();

		camera.setParameters(parameters);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// We purposely disregard child measurements because act as a
		// wrapper to a SurfaceView that centers the camera preview instead
		// of stretching it.
		final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
		final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
		setMeasuredDimension(width, height);

		if (supportedPreviewSizes != null) {
			cameraPreviewSize = getOptimalPreviewSize(supportedPreviewSizes, width, height);
		}
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		if (changed && getChildCount() > 0) {
			final View child = getChildAt(0);

			final int width = r - l;
			final int height = b - t;

			int previewWidth = width;
			int previewHeight = height;
			
			if (cameraPreviewSize != null) {
				previewWidth = cameraPreviewSize.width;
				previewHeight = cameraPreviewSize.height;
			}

			// Center the child SurfaceView within the parent.
			if (width * previewHeight > height * previewWidth) {
				final int scaledChildWidth = previewWidth * height / previewHeight;
				child.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height);
				
			} else {
				final int scaledChildHeight = previewHeight * width	/ previewWidth;
				child.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2);
			}
		}
	}

	public void surfaceCreated(SurfaceHolder holder) {
		// The Surface has been created, acquire the camera and tell it where to draw.
		
		try {
			if (camera != null) {
				camera.setPreviewDisplay(holder);
			}
			
		} catch (IOException exception) {
			Log.e(TAG, "IOException caused by setPreviewDisplay()",	exception);
		}
	}

	public void surfaceDestroyed(SurfaceHolder holder) {
		// Surface will be destroyed when we return, so stop the preview.
		if (camera != null) {
			camera.stopPreview();
		}
	}

	private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
		final double ASPECT_TOLERANCE = 0.1;
		double targetRatio = (double) w / h;
		if (sizes == null)
			return null;

		Size optimalSize = null;
		double minDiff = Double.MAX_VALUE;

		int targetHeight = h;

		// Try to find an size match aspect ratio and size
		for (Size size : sizes) {
			double ratio = (double) size.width / size.height;
			if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
				continue;
			if (Math.abs(size.height - targetHeight) < minDiff) {
				optimalSize = size;
				minDiff = Math.abs(size.height - targetHeight);
			}
		}

		// Cannot find the one match the aspect ratio, ignore the
		// requirement
		if (optimalSize == null) {
			minDiff = Double.MAX_VALUE;
			for (Size size : sizes) {
				if (Math.abs(size.height - targetHeight) < minDiff) {
					optimalSize = size;
					minDiff = Math.abs(size.height - targetHeight);
				}
			}
		}
		return optimalSize;
	}

	public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
		// Now that the size is known, set up the camera parameters and begin the preview.
		Camera.Parameters parameters = camera.getParameters();
		parameters.setPreviewSize(cameraPreviewSize.width, cameraPreviewSize.height);
		requestLayout();

		camera.setParameters(parameters);
		camera.startPreview();
	}
}
